// Copyright (C) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;

using System.Collections;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Xml;
using System.Data;
using System.Security.Permissions;
using System.Globalization;
using System.DirectoryServices;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Server;
using tfp = Microsoft.TeamFoundation.Proxy;
using Microsoft.TeamFoundation.Controls;
using Microsoft.DDSolutions.SqlReportServer;


[assembly: CLSCompliant(true)]
[assembly: SecurityPermission(SecurityAction.RequestMinimum)]

namespace Microsoft.DDSolutions.TfsAdmin
{
    /// <summary>
    ///   Using MVC Design pattern. The following Controller class encapsulates all the API's needed on the GUI side. 
    /// 
    ///   TODO: 
    ///         The sub Systems are currently named strings like "SharePoint","ReportServer". Change this to 
    /// use enum instead. 
    ///         Move all other strings to Resource.
    /// </summary>
    class Controller : IDisposable 
    {
        private enum DomainUserName { Domain = 0, Name = 1 };
        private Microsoft.DDSolutions.Tfs.TfsServerProxy tfs;
        private Microsoft.DDSolutions.SharePoint.SPServerProxy sharePointServer;
        //private Microsoft.DDSolutions.SqlReportServer.ReportingServiceProxy reportServer;
        private IReportService reportServer;
        private bool isProjectSite;

        private RoleConfig rc;

        [System.Diagnostics.CodeAnalysis.SuppressMessage ("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        //the above suppression is because the get is not called by anything 
        private string validatedSharePointSite;
        private bool validatedSharePointSiteStatus;

        private string validatedReportingServicesSite;
        private bool validatedReportingServicesSiteStatus;

        public bool IsProjectSite
        {
            get { return isProjectSite; }
            set 
            { 
                isProjectSite = value;
                sharePointServer.IsProjectSite = value;
                reportServer.IsProjectSite = value;
                tfs.IsProjectSite = value;
            }
        }
        /// <summary>
        ///  Takes a TeamFoundationServer instance that should already be authenticated
        ///  and returns a readonly collection of team project names. Connect will throw 
        /// an exception in case of invalid TFS server and its caught in the GUI layer so 
        /// that a MessageBox can be displayed.
        /// </summary>
        /// <param name="tfsAppServer">An instance of a TeamFoundationServer</param>
        /// <returns>ReadOnlyCollection of team project names</returns>
        public ReadOnlyCollection<string> OnTFSLogin(TeamFoundationServer tfsAppServer)
        {
            ReadOnlyCollection<string> projects = new ReadOnlyCollection<string>(new Collection<string>());


            tfs = new Microsoft.DDSolutions.Tfs.TfsServerProxy();
            try
            {
                tfs.Connect(tfsAppServer);
            }
            catch (System.Web.Services.Protocols.SoapException se)
            {
                if (Microsoft.DDSolutions.Tfs.TfsServerProxy.IsServerClientMismatch(se.Message) == true)
                {
                    throw new Microsoft.DDSolutions.Tfs.TfsClientServerMismatchException
                        (MainResources.TFSClientServerMismatchErrorMsg, tfsAppServer.Name);
                }
                else
                    throw;
            }

            tfs.GetAllProjectInfo();
            projects = tfs.ProjectNames;
            
            sharePointServer = new Microsoft.DDSolutions.SharePoint.SPServerProxy(tfs.BaseSiteUrl.ToString(), tfs.AdminServiceUrl.ToString());

            // For Beta 3 one would use tfsDataTierName = tfs.GetTFSDataTierHostName() and pass that as the host name
            // to the constructor of ReportingServiceProxy below.
            // With RC release however the Reporting Services has moved to the App Tier, so pass in the AppServer Host name
            // instead of data tier host name.

            reportServer = ReportServiceFactory.CreateReportService(tfs.ReportServiceUrl.ToString());

            // Removed the creation of the RoleConfig instance from this point and moved the process to the method
            // PrepRoleConfig so that the MainForm instance can call for the parsing of the RoleConfig.xml file
            // when the team project is selected thus being able to evaluate if the team project SharePoint site 
            // is a WSS 3.0 site
            // rc = new RoleConfig();

            return projects;
        }

        /// <summary>
        ///   Returns a Dataset needed for the GUI to show All users and their respective roles
        /// across all the 3tiers. 
        /// 
        ///  Dataset has a table named TfsUserAndRoles. The schema looks like the following
        /// 
        ///     User       TFSRole                SPRoles                           RSRoles
        ///  Domain\user   Contributors        Contributor                     Browser;Publisher
        /// 
        ///  TODO: Need to consider making this a typed DataSet later.
        ///        Move all strings to Resource 
        /// </summary>
        /// <param name="nodeName">Team Project Name</param>
        /// <returns>DataSet</returns>
        public DataSet GetDataSet(string nodeName)
        {
            Collection<Microsoft.DDSolutions.Tfs.TfsUser> Users = tfs.GetAllUsers(nodeName);

            DataSet ds = new DataSet("UsersAndRoles");
            ds.Locale = CultureInfo.CurrentUICulture;
            ds.Tables.Add("TfsUserAndRoles");
            ds.Tables[0].Columns.Add("Status");
            ds.Tables[0].Columns.Add("User");
            ds.Tables[0].Columns.Add("DisplayName");
            ds.Tables[0].Columns.Add("TFSRole");
            ds.Tables[0].Columns.Add("SPRoles");
            ds.Tables[0].Columns.Add("RSRoles");
            ds.Tables[0].Columns.Add("MarkDelete", System.Type.GetType("System.Boolean"));
            ds.Tables[0].PrimaryKey = new DataColumn[] { ds.Tables[0].Columns["User"] };

            if (Users.Count == 0)
            {
                DataRow row = ds.Tables[0].NewRow();
                row["Status"] = "I"; //. I - Initial State, A - Add, D - Delete , C - Change, E - Error

                //added a column for a friendly name display 
                row["User"] = "";
                row["DisplayName"] = "";
                ds.Tables[0].Rows.Add(row);
            }
            else
            {
                foreach (Microsoft.DDSolutions.Tfs.TfsUser tUser in Users)
                {
                    string tfsUser = tUser.Name;
                    string tfsUserDisplayName = tUser.DisplayName;

                    string tfsRolesStr = GetAsDelimitedString(tUser.TfsRoles, ';');
                    string spRolesStr = "";
                    string rsRolesStr = "";

                    if (validatedSharePointSiteStatus)
                    {
                        /// Get Share Point Roles of the user as delimited string
                        spRolesStr = GetRolesOfUserAsDelimitedStringBySystem(tfsUser, nodeName, "SharePoint");
                    }

                    if (validatedReportingServicesSiteStatus)
                    {
                        /// Get Report Server Roles of the user as delimited string
                        rsRolesStr = GetRolesOfUserAsDelimitedStringBySystem(tfsUser, nodeName, "ReportServer");
                    }
                    
                    DataRow row = ds.Tables[0].NewRow();
                    row["Status"] = "I"; //. I - Initial State, A - Add, D - Delete , C - Change, E - Error

                    //added a column for a friendly name display 
                    row["User"] = tfsUser;
                    row["DisplayName"] = tfsUserDisplayName;
                    row["TFSRole"] = tfsRolesStr;
                    row["SPRoles"] = spRolesStr;
                    row["RSRoles"] = rsRolesStr;
                    row["MarkDelete"] = false;

                    ds.Tables[0].Rows.Add(row);
                }
            }
            return ds;
        }
        /// <summary>
        /// Displays the AddGroup Dialog
        /// </summary>
        /// <param name="parentWin"></param>
        /// <param name="tfsServer"></param>
        /// <returns></returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public Dictionary<string, string> ShowAddGroupControl(System.Windows.Forms.IWin32Window parentWin, TeamFoundationServer tfsServer)
        {
            Dictionary<string, string> userGroupCollection = null;
            Identity[] groups;
            IGroupSecurityService tfsSecurityService = (IGroupSecurityService)tfsServer.GetService(typeof(IGroupSecurityService));
            AddGroupControl.AddWindowsUsersOrGroups(parentWin, tfsSecurityService, out groups);
            if (groups != null)
            {
                userGroupCollection = new Dictionary<string, string>();
                foreach (Identity identity in groups)
                {
                  string userName = String.Concat(identity.Domain, "\\", identity.AccountName);
                  userGroupCollection.Add(userName, GetFriendlyDisplayName(userName));
                }
            }
            return userGroupCollection;
        }
    
        /// <summary>
        ///  Helper function to convert the sub system user roles to a delimited string.
        /// </summary>
        /// <param name="userName">Name of the User</param>
        /// <param name="siteName">Site Name</param>
        /// <param name="subSystem">Sub System so that the respective web service can be accessed</param>
        /// <returns></returns>
        private string GetRolesOfUserAsDelimitedStringBySystem(string userName, string siteName, string subSystem)
        {
            string delimitedStr = "";

            ValidateUserProject(userName, siteName);

            if (subSystem == null)
                throw new ArgumentNullException("subSystem");

            if (subSystem.Equals("SharePoint") == true)
            {
                ReadOnlyCollection<string> spRoles = sharePointServer.GetRoleCollectionFromUser(userName, siteName);
                delimitedStr = GetAsDelimitedString(spRoles, ';');
            }
            else if (subSystem.Equals("ReportServer") == true)
            {
                ReadOnlyCollection<string> rsRoles = reportServer.GetUserRoles(userName, siteName);
                delimitedStr = GetAsDelimitedString(rsRoles, ';');
            }

            return delimitedStr;
        }

        /// <summary>
        ///  Helper function to to the actual conversion from a list of string to delimited string.
        /// </summary>
        /// <param name="strs">A collection of strings</param>
        /// <param name="delimiter">Delimiter</param>
        /// <returns>A delimited string</returns>
        private static string GetAsDelimitedString(ICollection<string> strs, char delimiter)
        {
            string delimitedStr = "";
            if (strs != null)
            {
                foreach (string str in strs)
                {
                    delimitedStr = delimitedStr + str + delimiter;
                }

                delimitedStr = RemoveLastDelimiter(delimitedStr, delimiter);
            }
            return delimitedStr;
        }

        /// <summary>
        ///  Returns Invalid roles based on default mappings specified by the user for a given sub system
        ///   - SharePoint, ReportServer
        /// </summary>
        /// <param name="nodeName">Team Project Name</param>
        /// <param name="system">"SharePoint" or "Reportserver"</param>
        /// <returns>ReadOnlyCollection of Invalid Role names</returns>
        public ReadOnlyCollection<string> GetInvalidRolesFromMappings(string nodeName, string system)
        {
            ReadOnlyCollection<string> TfsRoles = tfs.GetProjectApplicationGroups(nodeName);
            Collection<string> InvalidRoles = new Collection<string>();

            foreach (string TfsRole in TfsRoles)
            {
                ReadOnlyCollection<string> sRoles = rc.GetRolesBySystem(TfsRole, system);

                if (sRoles == null)
                    return null;

                if (system.Equals("SharePoint") == true)
                {
                    foreach (string sRole in sRoles)
                    {
                        if (sharePointServer.IsRoleExist(sRole, nodeName) == false)
                            InvalidRoles.Add(sRole);
                    }
                }
                else if (system.Equals("ReportServer") == true)
                {
                    ReadOnlyCollection<string> rsRoles = reportServer.Roles;
                    foreach (string sRole in sRoles)
                    {
                        if (!rsRoles.Contains(sRole))
                            InvalidRoles.Add(sRole);
                    }
                }
            }

            return new ReadOnlyCollection<string>(InvalidRoles);
        }

        /// <summary>
        ///    Remove the last occurance of the delimter in the given string
        /// and return the new string.
        /// </summary>
        /// <param name="sValue">String from which the delimiter needs to be removed</param>
        /// <param name="delimter">Delimiter</param>
        /// <returns>Modified string</returns>
        private static string RemoveLastDelimiter(string sValue, char delimter)
        {
            int pos = sValue.LastIndexOf(delimter);

            if (pos > 0 && pos == sValue.Length - 1)
                sValue = sValue.Remove(pos, 1);

            return sValue;
        }

        /// <summary>
        ///   Verify if the Team Project has a connected SharePoint site.
        /// </summary>
        /// <param name="siteName">Name of the sharepoint site</param>
        /// <returns>True or False depending on wether or not the site exists</returns>
        public bool VerfiySharePointSiteExistance(string siteName)
        {
            if (siteName == null)
                throw new ArgumentNullException("siteName");

            if (validatedSharePointSite != siteName)
            {
                validatedSharePointSite = siteName;
                validatedSharePointSiteStatus = sharePointServer.VerifySiteExistance(siteName);
            }

            return validatedSharePointSiteStatus;
        }

        /// <summary>
        ///   Verify if the Team Project has a connected Reporting Services site.
        /// </summary>
        /// <param name="siteName">Name of the sharepoint site</param>
        /// <returns>True or False depending on wether or not the site exists</returns>
        public bool VerfiyReportingServicesSiteExistance(string siteName)
        {
          if (siteName == null)
            throw new ArgumentNullException("siteName");

          if (validatedReportingServicesSite != siteName)
          {
            validatedReportingServicesSite = siteName;
            validatedReportingServicesSiteStatus = reportServer.VerifySiteExistance(siteName);
          }

          return validatedReportingServicesSiteStatus;
        }

        /// <summary>
        ///   Get the SharePoint Roles from the SharePoint site.
        /// </summary>
        /// <param name="siteName">Name of the sharepoint site</param>
        /// <returns>ReadonlyCollection of SharePoint Roles</returns>
        public ReadOnlyCollection<string> GetSharePointRoles(string siteName)
        {
            if (siteName == null)
                throw new ArgumentNullException("siteName");

            return sharePointServer.GetRoles(siteName);
        }

        /// <summary>
        ///   Get the Reporting Services Roles
        /// </summary>
        /// <returns>Readonly collection of Report Server roles</returns>
        public ReadOnlyCollection<string> GetReportingServicesRoles()
        {
            return reportServer.Roles;
        }

        /// <summary>
        ///   Get the DisplayName of the user 
        /// </summary>
        /// <param name="userDomainName">Pass in the domain user name like domain\\username</param>
        /// <returns>DisplayName if the user exist or string.Empty if the user doesn't exist</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public string GetFriendlyDisplayName(string userDomainName)
        {
            if (string.IsNullOrEmpty(userDomainName) == true)
                throw new ArgumentNullException("userDomainName");

            string[] domainNameParts = userDomainName.Split('\\');

            string fullName = string.Empty;
            
            string path = "WinNT://" +
                            domainNameParts[(int)DomainUserName.Domain] +
                            "/" +
                            domainNameParts[(int)DomainUserName.Name];
            try
            {
                using (DirectoryEntry domain = new DirectoryEntry(path))
                {
                    if (domain != null && domain.Properties["FullName"].Value != null)
                    {
                        fullName = domain.Properties["FullName"].Value.ToString();
                    }
                }
            }
            catch (System.Runtime.InteropServices.COMException ce)
            {
                if (ce != null)
                    fullName = null;
            }

            return fullName;
        }

        /// <summary>
        /// <summary>
        ///  Add a given user to a SharePoint site with the given role.
        /// </summary>
        /// <param name="userName">User Name</param>
        /// <param name="role">SharePoint site role</param>
        /// <param name="siteName">SharePoint Site Name</param>
        public void AddSharePointUserToRole(string userName, string role, string siteName)
        {
            ValidateUserProjectRole(userName, siteName, role);

            Microsoft.TeamFoundation.Server.Identity ident = tfs.GetMemberIdentity(userName);
            if (ident == null)
                throw new Microsoft.DDSolutions.Tfs.InvalidTfsUserException(userName);
            
            sharePointServer.AddUser(userName, siteName, role, ident.DisplayName, ident.MailAddress, ident.Description);
        }

        /// <summary>
        ///    Remove the user from a Role on a given SharePoint site
        /// </summary>
        /// <param name="userName">User Name</param>
        /// <param name="role">Name of the role</param>
        /// <param name="siteName">Name of SharePoint Site</param>
        public void RemoveSharePointUserFromRole(string userName, string role, string siteName)
        {
            ValidateUserProjectRole(userName, siteName, role);
            sharePointServer.RemoveUserFromRole(userName, siteName, role);
        }

        /// <summary>
        ///   Add the user to Reporting Services site with given user role.
        /// </summary>
        /// <param name="userName">User Name</param>
        /// <param name="roleName">Reporting Services Role Name</param>
        /// <param name="siteName">Folder or Project Name</param>
        public void AddUserToReportServer(string userName, string roleName, string siteName)
        {
            ValidateUserProjectRole(userName, siteName, roleName);
            reportServer.AddUser(userName, siteName, roleName);
        }

        /// <summary>
        ///   Remove the user from a given role on a given ReportingServices server.
        /// </summary>
        /// <param name="userName">UserName</param>
        /// <param name="roleName">Reporting Services Role Name</param>
        /// <param name="siteName">Folder or Project Name</param>
        public void RemoveUserFromReportServer(string userName, string roleName, string siteName)
        {
            ValidateUserProjectRole(userName, siteName, roleName);
            reportServer.RemoveUserFromRole(userName, siteName, roleName);
        }

        /// <summary>
        /// Get TFS Roles for a given team project. The call to GetProjectApplicationGroups will throw 
        /// exception for invalid nodeName parameter.
        /// </summary>
        /// <param name="nodeName">Team Project Name</param>
        /// <returns>ReadOnlyCollection of TFS Role Names</returns>
        public ReadOnlyCollection<string> GetTfsRoles(string nodeName)
        {
            return tfs.GetProjectApplicationGroups(nodeName);
        }

        /// <summary>
        ///  Add user to TFS role on a TFS Team Project
        /// </summary>
        /// <param name="userName">User Name</param>
        /// <param name="roleName">Role Name</param>
        /// <param name="nodeName">Team Project Name</param>
        public void AddUserToTfsRole(string userName, string roleName, string nodeName)
        {
            ValidateUserProjectRole(userName, nodeName, roleName);
            tfs.AddUserToRole(userName, roleName, nodeName);
        }

        /// <summary>
        ///   Remove the TFS User. This needs to not only remove the user from TFS but also
        /// from associated SharePoint and ReportingServices
        /// </summary>
        /// <param name="userName">User Name</param>
        /// <param name="nodeName">Team Project Name</param>

        public void RemoveTFSUser(string userName, string nodeName)
        {
            ValidateUserProject(userName, nodeName);
            if (tfs.IsUserExist(userName) == true)
            {
                tfs.RemoveUser(userName, nodeName);

                if (validatedReportingServicesSiteStatus)
                    reportServer.RemoveUser(userName, nodeName);

                if (validatedSharePointSiteStatus)
                    sharePointServer.RemoveUser(userName, nodeName);
            }
        }

        /// <summary>
        ///    Each TFS role could me mapped to a set of roles in SharePoint and Reporting Services
        /// by the user using the XML Configuration file. This API gets the roles as a delimited string
        /// so that it can be used to set the roles in the GUI when the user selects a TFS role for the user.
        /// </summary>
        /// <param name="tfsRole"></param>
        /// <param name="subSystem"></param>
        /// <returns></returns>
        public string GetMappedRoleStringBySystem(string tfsRole, string subSystem)
        {
            string newRoles = "";

            if (IsSubSystemAvailable(subSystem))
            {
                ReadOnlyCollection<string> roles = rc.GetRolesBySystem(tfsRole, subSystem);
                newRoles = GetAsDelimitedString(roles, ';');
            }

            return newRoles;
        }

        /// <summary>
        /// Check to see if we have a valid connection to the various sub systems   
        /// </summary>
        /// <param name="subSystem"></param>
        /// <returns></returns>
        private bool IsSubSystemAvailable(string subSystem)
        {
            if (subSystem == null)
                throw new ArgumentNullException("subSystem");

            bool subSystemAvailablity = true;

            if (subSystem.Equals("SharePoint") == true)
                subSystemAvailablity = validatedSharePointSiteStatus;
            else if (subSystem.Equals("ReportServer") == true)
                subSystemAvailablity = validatedReportingServicesSiteStatus;

            return subSystemAvailablity;
        }

        /// <summary>
        ///  Validation helper function for often used parameters
        /// </summary>
        /// <param name="userName">User Name</param>
        /// <param name="nodeName">Project or Folder name</param>
        /// <param name="roleName">Role of the user</param>
        private static void ValidateUserProjectRole(string userName, string nodeName, string roleName)
        {
            ValidateUserProject(userName, nodeName);

            if (string.IsNullOrEmpty(roleName))
                throw new ArgumentNullException("roleName");
        }

        /// <summary>
        ///  Validation helper function for often used parameters
        /// </summary>
        /// <param name="nodeName">Project or Folder name</param>
        /// <param name="roleName">Role of the user</param>
        private static void ValidateUserProject(string userName, string nodeName)
        {
            if (string.IsNullOrEmpty(userName))
                throw new ArgumentNullException("userName");

            if (string.IsNullOrEmpty(nodeName))
                throw new ArgumentNullException("nodeName");

        }

        /// <summary>
        /// This method will parse the RoleConfig.xml file via the RoleConfig class. It uses the team project name
        /// to determine the team project SharePoint site and thus determine if the site if a WSS 3.0 site for the 
        /// proper parsing of the RoleConfig.xml file
        /// </summary>
        /// <param name="nodeName">Takes the name of the team project to set the team project SharePoint site URL</param>
        internal void PrepRoleConfig(string nodeName)
        {
            sharePointServer.SetUrl(nodeName);
            rc = new RoleConfig(sharePointServer.isWss3());

        }

        #region IDisposable Members
        private bool _disposed;
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed == true) return;

            if (disposing)
            {
                if (sharePointServer!= null) sharePointServer.Dispose();
                _disposed = true;
            }
        }

        #endregion
    }
}
